package com.szwistar.common.luaj;

import java.io.File;
import java.io.InputStream;

import org.apache.log4j.PropertyConfigurator;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.szwistar.common.datastruct.Utils;

public class ThreadLocalLuaState extends LuaState implements LuaResourceFinder {
	static final Logger LOG = LoggerFactory.getLogger(ThreadLocalLuaState.class);
	static final Logger LOGLUA = LoggerFactory.getLogger("lua");

	// 用于保存线程专用 LuaState 的线程局部存储
	private static final ThreadLocal<ThreadLocalLuaState> threadLuaState = new ThreadLocal<ThreadLocalLuaState>();
	// LuaState 初始化回调接口
	private static LuaStateInitializer luaStateInitializer = null;

	/**
	 * 设置 LuaState 初始化回调接口
	 * @param luaStateInitializer
	 */
	public static void setLuaStateInitializer(LuaStateInitializer luaStateInitializer) {
		ThreadLocalLuaState.luaStateInitializer = luaStateInitializer;
	}

	/**
	 * 获取当前线程绑定的 Lua 执行环境
	 * 注意：因为 LuaState 不能跨线程访问，所以如果某个线程需要执行脚本，就必须创建自己的 LuaState
	 */
	public static ThreadLocalLuaState get() {
		// 从线程局部存储中获取当前线程的 LuaState，如没有，就说明是第一次访问，需要为此线程新建一个
		ThreadLocalLuaState lua = threadLuaState.get();
		if(lua != null) { return lua; }
		LOG.debug("!!!初始化线程 {}#{} 内的 Lua 解释器 ...", Thread.currentThread().getId(), Thread.currentThread().getName());

		try {
			// 新建 LuaState
			lua = new ThreadLocalLuaState();

			// 设置资源加载器
			lua.finder = lua;

			// 加载标准包
			lua.openLibs();

			// 加载自己的工具包
			lua.load(new LuaUtils());

			// 调用初始化接口进行额外的初始化
			if (luaStateInitializer != null) {
				luaStateInitializer.init(lua);
			}

			// 保存到线程局部存储
			threadLuaState.set(lua);
			LOG.debug("!!!初始化 Lua 解释器成功！");
			return lua;
		} catch(Exception e) {
			LOG.error("!!!初始化 Lua 解释器失败！", e);
			return null;
		}
	}

	public void setGlobal(String varName, Object value) {
		if(Utils.isEmpty(varName)) { return; }
		LuaValue lvobj = CoerceJavaToLua.coerce(value);
		rawset(varName, lvobj);
	}

	// 执行脚本函数
	@SuppressWarnings("unchecked")
	protected <T> T execLua(LuaValue chunk, Class<T> retClazz) throws Exception {
		LuaValue lvRet = chunk.call();
		Object ret = LuaUtils.toJava(lvRet);
		//LOGLUA.debug("!!!执行 Lua 脚本返回值类型: {}", (ret == null ? "null" : ret.getClass().getSimpleName()));
		return (T)ret;
	}

	/**
	 * 执行 Lua 脚本
	 * @param script
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T execLua(String chunkName, String script, Class<T> retClazz) throws Exception {
		if(Utils.isEmpty(script)) { return null; }
		LOGLUA.debug("!!!在线程 {}#{} 中执行 Lua 脚本: \n\t{}", Thread.currentThread().getId(), Thread.currentThread().getName(), script);

		// 加载脚本（实际上加载为一个函数）
		chunkName = (chunkName == null) ? "_script@"+Thread.currentThread().getName() : chunkName;
		LuaValue chunk = load(script, chunkName);

		// 执行脚本函数
		return execLua(chunk, (retClazz == null) ? (Class<T>)Object.class : retClazz);
	}

	/**
	 * 执行 Lua 脚本文件
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	public <T> T execLua(File scriptFile, Class<T> retClazz) throws Exception {
		LOGLUA.debug("!!!在线程 {} 中执行 Lua 脚本文件: \n\t'{}'", Thread.currentThread().getName(), scriptFile);

		// 加载脚本（实际上加载为一个函数）
		LuaValue chunk = loadfile(scriptFile.getAbsolutePath());

		// 执行脚本函数
		return execLua(chunk, (retClazz == null) ? (Class<T>)Object.class : retClazz);
	}

	@Override
	public InputStream findResource(String filename) {
		// TODO 打开指定资源
		return null;
	}


	// for test only
	// 请把运行目录指定为 common/test
	@SuppressWarnings("unused")
	public static void main(String[] args) {
		PropertyConfigurator.configure("log4j.pc.properties");
		ThreadLocalLuaState lua = ThreadLocalLuaState.get();

		lua.setGlobal("tester1", new JavaTester(1));
		lua.setGlobal("tester2", new JavaTester(2));
		JavaTester tester3 = new JavaTester(3);

		LuaValue chunk = lua.loadfile("tests/hello.lua");
		LuaValue lvRet = chunk.call();
		// 对于 chunk，无法传递命名参数，只能通过 setGlobal() 设置全局变量
		//LuaValue lvRet = chunk.call(valueOf(123), valueOf("York"), CoerceJavaToLua.coerce(tester3));

		// 返回值处理: call() 只能返回第一个 lua 脚本返回值
		Object ret = LuaUtils.toJava(lvRet);
		LOGLUA.debug("脚本返回：类型 = '{}', 值 = '{}'", (ret == null) ? null : ret.getClass(), ret);
	}

	public static class JavaTester {
		int id = 0;

		public JavaTester(int id) {
			super();
			this.id = id;
		}

		@SuppressWarnings("unused")
		public void hello(String name) {
			System.out.printf("JavaTester[%d]: 你好 abc123, %s !\r\n", id, name);
		}
	}
}
